#ifndef __MESH_H__
#define __MESH_H__

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <vector>
#include <string>
#include <glad/glad.h>
// #include <GL/glew.h>

struct Vertex{
    glm::vec4 position;
    glm::vec3 normal;
    glm::vec2 texCoord;
};

class Mesh{
    protected:
        std::vector<Vertex> vertices;
        std::vector<GLuint> indices;

        GLuint VAO, VBO, EBO;
               
    public:
        void generateMesh(int iSlices);

        void setVertices(std::vector<Vertex> && v){
            vertices = v;
        }

        void setIndices(std::vector<GLuint> && I){
            indices = I;
        }

        void setup(){
            glCreateVertexArrays(1, &VAO);
            glBindVertexArray(VAO);
            glCreateBuffers(1, &VBO);
            glBindBuffer(GL_ARRAY_BUFFER, VBO);
            glNamedBufferStorage(VBO, sizeof(Vertex)*vertices.size(), &vertices[0], 0);
            glGenBuffers(1, &EBO);
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
            glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(float)*indices.size(), &indices[0], GL_STATIC_DRAW);
            glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
            glEnableVertexAttribArray(0);
            glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, normal));
            glEnableVertexAttribArray(1);
            glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, texCoord));
            glEnableVertexAttribArray(2);
        }

        void render(){
            glBindVertexArray(VAO);
            glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
        }

};

class Plane: public Mesh{
    public:
        void generateMesh(int iSlices){
            int n = iSlices + 1;
            float s = 2.0f/(float)iSlices;
            Vertex temp_vertex;
            for(int i=0; i<n; i++){
                for(int j=0; j<n; j++){
                    temp_vertex.position = glm::vec4(s*j - 1.0f, s*i - 1.0f, 0.0f, 1.0f);
                    temp_vertex.normal = glm::vec3(0.0f, 0.0f, 1.0f);
                    temp_vertex.texCoord = glm::vec2(1.0f/(float)iSlices * j, 1.0f/(float)iSlices * i);
                    vertices.push_back(temp_vertex);
                }
            }
            
            for(int i=0; i<iSlices; i++){
                for(int j=0; j<iSlices; j++){
                    indices.push_back(i*n + j);
                    indices.push_back((i+1)*n + j+1);
                    indices.push_back((i+1)*n + j);

                    indices.push_back(i*n + j);
                    indices.push_back(i*n + j + 1);
                    indices.push_back((i+1)*n + j+1);
                }
            }
        }
};

class Sphere: public Mesh{
    public:
        void generateMesh(int iSlices){
            int m = iSlices/2 + 1;
            int n = iSlices+1;
            float s = 360.0f/(float)iSlices;
            glm::vec4 up(0.0f, 1.0f, 0.0f, 1.0f);
            glm::mat4 I(1.0f);
            glm::vec3 X(1.0f, 0.0f, 0.0f);
            glm::vec3 Y(0.0f, 1.0f, 0.0f);
            glm::vec3 Z(0.0f, 0.0f, 1.0f);
            Vertex temp_vertex;
            for(int i=0; i<m; i++){
                for(int j=0; j<n; j++){
                    temp_vertex.position = glm::rotate(I, glm::radians(s*j), Y) * glm::rotate(I, glm::radians(s*i), Z) * up;
                    temp_vertex.normal = temp_vertex.position;
                    temp_vertex.texCoord = glm::vec2(1.0f/(float)(n-1) * j, 1.0f/(float)(m-1) * i);
                    vertices.push_back(temp_vertex);
                }
            }
            
            for(int i=0; i<m-1; i++){
                for(int j=0; j<n-1; j++){
                    indices.push_back(i*n + j);
                    indices.push_back((i+1)*n + j);
                    indices.push_back((i+1)*n + j+1);

                    indices.push_back(i*n + j);
                    indices.push_back((i+1)*n + j+1);
                    indices.push_back(i*n + j + 1);                   
                }
            }
        }
};

class Torus: public Mesh{
    public:
        void generateMesh(int iSlices){
            int n = iSlices + 1;
            float s = -360.0f/(float)iSlices;
            glm::vec4 top(0.0f, 0.2f, 0.0f, 1.0f);
            glm::vec4 normal_up(0.0f, 1.0f, 0.0f, 1.0f);
            glm::mat4 I(1.0f);
            glm::vec3 X(1.0f, 0.0f, 0.0f);
            glm::vec3 Y(0.0f, 1.0f, 0.0f);
            glm::vec3 Z(0.0f, 0.0f, 1.0f);
            Vertex temp_vertex;
            for(int i=0; i<n; i++){
                for(int j=0; j<n; j++){
                    temp_vertex.position = glm::rotate(I, glm::radians(s*j), Z) 
                                            * glm::translate(I, glm::vec3(0.0f, 0.8f, 0.0f))
                                            * glm::rotate(I, glm::radians(s*i), X) 
                                            * top;
                    temp_vertex.normal = glm::rotate(I, glm::radians(s*j), Z) 
                                            * glm::rotate(I, glm::radians(s*i), X) 
                                            * normal_up;
                    temp_vertex.texCoord = glm::vec2(1.0f/(float)iSlices * j * 4, 1.0f/(float)iSlices * i);
                    vertices.push_back(temp_vertex);
                }

            }
            for(int i=0; i<iSlices; i++){
                for(int j=0; j<iSlices; j++){
                    indices.push_back(i*n + j);
                    indices.push_back((i+1)*n + j+1);
                    indices.push_back((i+1)*n + j);

                    indices.push_back(i*n + j);
                    indices.push_back(i*n + j + 1);
                    indices.push_back((i+1)*n + j+1);
                }
            }
        }
};

#endif

